
#include "precompiled.h"
#include "httpserver.h"

#include <QFileInfo>
#include <QFileIconProvider>

#include <logger/logger.h>
#include "helper.h"
#include "configstack.h"

class HttpHeader{
public:
    QString file;
    bool get;
    QMap<QString, QString> params;
};

class HttpServerPrivate{
public:
    HttpServerPrivate() {


    }
    ~HttpServerPrivate() {}
    bool disabled;
    int port;
    QString autoindex;
    QDir root;
    QMap<QString, QString> mime;
};

HttpServer::HttpServer(const QString& configKey, QObject *parent) :
        QTcpServer(parent), O_PRIVATE_CREATE(HttpServer)
{
    O_D(HttpServer);

    LI.Info() << "Http server initialize";

    d->disabled =!          conf(configKey, "enabled", false).toBool();
    d->root = QDir(         conf(configKey, "root", "html").toString());
    d->autoindex =          conf(configKey, "autoindex").toString();
    d->port =               conf(configKey, "port", 8080).toInt();
    QString mimeFileName =  conf(configKey, "mimeFile", ":/other/mimelist").toString();

    LI.Debug() << "Config:";
    LI.Debug() << "disabled:" << d->disabled;
    LI.Debug() << "root:" << d->root.absolutePath();
    LI.Debug() << "port:" << d->port;


    QFile mimes(mimeFileName);
    mimes.open(QIODevice::ReadOnly);
    QTextStream all(&mimes);
    while(!all.atEnd()){
        QString line = all.readLine();
        if (line.isEmpty())
            continue;
        QStringList l = line.split("=", QString::SkipEmptyParts);
        d->mime.insert(l[0], l[1]);        
    }
}

HttpServer::~HttpServer()
{
    O_PRIVATE_DELETE;
}

void HttpServer::start()
{
    O_D(HttpServer);
    if (!d->disabled){
        if (listen(QHostAddress::Any, d->port)){
            LI.Info() << QString("Http Start on host:%1 ,port:%2").arg(serverAddress().toString()).arg(serverPort());
        }else{
            LI.Error() << "Http unable to start:" << errorString();
        }
    }else{
        LI.Info() << "Http Server is disabled. you can enable it by set section \"http {enabled:true}\" in config file";
    }
}

void HttpServer::incomingConnection(int socket){

    O_D(HttpServer);
    if (d->disabled)
        return;

    QTcpSocket* s = new QTcpSocket(this);
    connect(s, SIGNAL(readyRead()), this, SLOT(clientRead()));
    connect(s, SIGNAL(disconnected()), this, SLOT(discardClient()));
    s->setSocketDescriptor(socket);
    LI.Trace() << "New Connection:" << s->socketDescriptor() << " From:" << s->peerAddress().toString();
}

void HttpServer::sendHeader(QTcpSocket* socket, int code/* = 200*/, const QString& type/* = ""*/, const QByteArray& content/* = QByteArray()*/){
    QTextStream os(socket);
    os.setAutoDetectUnicode(true);
    QString codeDesc;

    switch(code){
    case 404:
        codeDesc = "Not Found";
        break;
    default:
        codeDesc = "Ok";
    }

    os << "HTTP/1.0 " << code << " " << codeDesc << "\r\n";
    if (!type.isEmpty()){
        os << "Content-Type: " << type << "\r\n";
    }

    if (code == 404){
        os << "Location: /error/404/404.html" << "\r\n";
    }
    if (!content.isEmpty()){
        os << "Content-Length: " << content.length() << "\r\n";
    }
    os << "\r\n";
    os.flush();
    if (!content.isEmpty()){
        socket->write(content);
    }
}

HttpHeader parseHeader(const QStringList& header){

    HttpHeader httph;
    QStringList tokens = header[0].split(QRegExp("[ \r\n][ \r\n]*"));
    QString request = tokens[1];
    httph.get = tokens[0] == "GET";

    QString args;
    if (httph.get){
        QStringList t = request.split("\?");
        httph.file = t[0].remove(0, 1);
        if (t.length() > 1)
            args = t[1];
    }else{
        httph.file = request.remove(0, 1);
        args = header.last();
    }

    if (!args.trimmed().isEmpty()){
        QStringList argsList = args.split("&");
        foreach(QString s, argsList){
            QStringList kp = s.split("=");
            httph.params.insert(kp[0], kp[1]);
        }
    }

    return httph;
}

void HttpServer::processRequest(QTcpSocket* socket){

    O_D(HttpServer);

    LI.Debug() << "processRequest";

    QStringList headerItems;
    while (!socket->atEnd()){
        QString headerLine = socket->readLine();
        LI.Trace() << "Header line:" << headerLine;
        headerItems.append(headerLine);
    }

    HttpHeader header = parseHeader(headerItems);

    QString path;
    if (header.file.isEmpty()){
       header.file = d->autoindex;
    }

    QFile file(d->root.absoluteFilePath(header.file));

    LI.Debug() << "Request file:" << file.fileName();

    if (!file.open(QIODevice::ReadOnly)){        
        sendHeader(socket, 404);
    }else{
        QFileInfo fi(file);
        QString suff = fi.suffix();
        QString type = d->mime.value(suff);
        sendHeader(socket, 200, type, file.readAll());
    }

    LI.Debug() << "Wrote to client:"  << socket->peerAddress() << " file:" << path;
}

void HttpServer::clientRead(){

    O_D(HttpServer);
    if (d->disabled)
        return;

    QTcpSocket * socket = (QTcpSocket*)sender();
    if (socket->canReadLine()){
        processRequest(socket);
        socket->close();
        if (socket->state() == QTcpSocket::UnconnectedState) {
            LI.Debug() << "Connection closed:" << socket->socketDescriptor();
            delete socket;
        }

    }
}

void HttpServer::discardClient(){
    QTcpSocket* socket = (QTcpSocket*)sender();
    LI.Debug() <<  "Connection closed:" << socket->socketDescriptor();
    socket->deleteLater();
}


QString HttpServer::js(){
    QFile jquery(":/javascript/jquery");
    if (!jquery.open(QIODevice::ReadOnly))
        return QString();

    QTextStream js(&jquery);
    QString j = js.readAll();
    return j;
}

QString HttpServer::html(bool dynamic/* = true*/){
    QString h;
    QTextStream ht(&h);
    ht.setCodec("UTF-8");

    ht <<
            "<?xml version=\"1.0\" encoding=\"utf-8\"?> "
            "<?xml-stylesheet type=\"text/css\" href=\"style.css\"?>"
            "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">"
            "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"pl\">"
            "<head>"
            "<title>Optra - Web Interface</title>"
            "<meta http-equiv=\"Content-Type\" content=\"application/xhtml+xml; charset=utf-8\"/>"
            "</head>"
            "<body>"

            "<script type=\"text/javascript\">"
            << js() <<
            "</script>"
            "</body>"
            "</html>";
    return h;
}
